package com.yonyou.iuap.ruleengine.ascmpt.relevant;

import com.alibaba.fastjson.JSON;
import com.yonyou.iuap.formula.common.SpringContextHolder;
import com.yonyou.iuap.ruleengine.common.GlobalException;
import com.yonyou.iuap.ruleengine.dto.relevant.RuleExtParamDto;
import com.yonyou.iuap.ruleengine.dto.relevant.RuleItemDto;
import com.yonyou.iuap.ruleengine.dto.rulemgmt.RelevantRuleGroupInfoDto;
import com.yonyou.iuap.ruleengine.dto.rulemgmt.RelevantRuleInfoDto;
import com.yonyou.iuap.ruleengine.dto.rulemgmt.RuleGroupItemDto;
import com.yonyou.iuap.ruleengine.enums.RelevantDataTypeEnum;
import com.yonyou.iuap.ruleengine.enums.RelevantRuleTypeEnum;
import com.yonyou.iuap.ruleengine.enums.RuleItemDataType;
import com.yonyou.iuap.ruleengine.enums.RuleSceneEnum;
import com.yonyou.iuap.ruleengine.model.rule_exe_carrier.*;
import com.yonyou.iuap.ruleengine.service.AppContextService;
import com.yonyou.iuap.ruleengine.service.pub.RuleCacheService;
import com.yonyou.iuap.ruleengine.service.relevant.RelevantRuleMgmtService;
import com.yonyou.iuap.ruleengine.utils.MyDomainUtil;
import com.yonyou.iuap.ruleengine.utils.RuleExpUtil;
import com.yonyou.iuap.ruleengine.utils.SdkRedisCacheUtil;
import com.yonyou.iuap.ruleengine.utils.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * TODO
 *
 * @author lihong
 * @date 2023/4/6 11:11
 */
@Component("iUap_YB_RuleEngine_RelevantRuleExecHandle")
public class RelevantRuleExecHandle {
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    AppContextService appContextService;


   private RelevantRuleMgmtService ruleMgmtService;

    public RelevantRuleExecCarrier getRelevantRuleExecCarrier(RuleExtParamDto ruleExtParamDto, RelevantRuleTypeEnum ruleType) {
        RelevantRuleExecCarrier instance = new RelevantRuleExecCarrier();
        instance.setRuleGroup(getRelevantRuleGroupInfo(ruleExtParamDto, ruleType));
        if(instance.getRuleGroup().getRuleType() != ruleType.getCode()) {
            throw new GlobalException("Rule type is not correct !");
        }
        instance.setSources(getSourceMap(ruleExtParamDto, instance.getRuleGroup().getDataType()));
        instance.setComCarrier(getRuleExecuteCarrier(ruleExtParamDto, instance.getSources(), instance.getRuleGroup()));
        return instance;
    }

    // 四种场景统一的执行入口，返回命中的规则对象（RelevantRuleInfoDto）
    public RelevantRuleInfoDto fireSuccess(RelevantRuleExecCarrier execCarrier) {
        for (RelevantRuleInfoDto rule : execCarrier.getRuleGroup().getRules()) { // 逐个规则判断，符合条件即返回
            if(RuleExpUtil.fireSuccess(rule.getRuleLhs(), execCarrier.getComCarrier())) {
                return rule;
            }
        }
        return null;
    }

    private RelevantRuleGroupInfoDto getRelevantRuleGroupInfo(RuleExtParamDto ruleExtParam, RelevantRuleTypeEnum ruleTypeEnum) {
//        return ruleMgmtService.getByRuleGroupId(ruleExtParam.getRuleId());
        // 先尝试获取缓存实现
        RuleCacheService ruleCacheService = appContextService.getInstance(RuleCacheService.class);

        RelevantRuleGroupInfoDto ruleGroupInfo = null;
        if(ruleCacheService == null) { // 1）缓存无实现（弃缓存交互逻辑），走RPC交互逻辑
            ruleGroupInfo = this.getRelevantRuleMgmtService().getByRuleGroupId(ruleExtParam.getRuleId());
        } else {                               // 2）缓存有实现（走缓存交互逻辑），兼RPC交互逻辑
            ruleGroupInfo = redisCacheHandle(ruleExtParam, ruleCacheService);
        }
        if(ruleGroupInfo == null) {
            ruleGroupInfo = tryGetMockRuleGroupInfo(ruleExtParam);
            if (ruleGroupInfo == null) { // 缓存+服务端+MOCK均未拿到规则，视为异常情况，将异常抛出！
                throw new GlobalException("didn't find any rule, input : " + JSON.toJSONString(ruleExtParam));
            }
        }
        return ruleGroupInfo;
    }

    private RelevantRuleGroupInfoDto tryGetMockRuleGroupInfo(RuleExtParamDto ruleExtParam) {
        try {
            // 规则组为空，尝试取模拟执行规则（专为本地执行用，服务端启动测试/日常环境，跑其他环境的规则）
            return (RelevantRuleGroupInfoDto) ruleExtParam.getContext().get("mockRuleGroupInfo");
        } catch (Exception e) {
            return null; // 模拟的都娶不到只能空手归了！简单粗暴
        }
    }

    // 缓存交互逻辑
    private RelevantRuleGroupInfoDto redisCacheHandle(RuleExtParamDto ruleExtParam, RuleCacheService ruleCacheService) {
        RelevantRuleGroupInfoDto ruleGroupInfo;
        String redisJsonStr = ruleCacheService.get(SdkRedisCacheUtil.getIdKey4Relevant(ruleExtParam.getRuleId().toString()));
        if(StringUtil.isEmpty(redisJsonStr)) { // 没拿到，继续从服务端获取（并tryCache）
            ruleGroupInfo = getFromServerAndTryCache(ruleExtParam, ruleCacheService);
        } else {
            try { // 尝试从缓存中获取，任何原因导致失败，则继续走RPC查库
                ruleGroupInfo = JSON.parseObject(redisJsonStr, RelevantRuleGroupInfoDto.class);
            } catch (Exception e) { // Redis中的数据有问题，删除并继续从服务端拉取
                ruleGroupInfo = getFromServerAndTryCache(ruleExtParam, ruleCacheService);
            }
        }
        return ruleGroupInfo;
    }

    // 从服务端获取，并缓存到Redis
    private RelevantRuleGroupInfoDto getFromServerAndTryCache(RuleExtParamDto ruleExtParam, RuleCacheService ruleCacheService) {
        String domain = MyDomainUtil.getDomain(applicationContext);
        RelevantRuleGroupInfoDto ruleGroupInfo = null;
        if(ruleCacheService != null && StringUtil.isNotEmpty(domain)) {
            Map<String, Object> context = new HashMap<>();
            context.put("domain", domain); // 携带domain获取规则组信息，服务端会记录，当这个规则变更时，调用买点接口清除缓存
            ruleGroupInfo =  this.getRelevantRuleMgmtService().getByRuleGroupIdWillCache(ruleExtParam.getRuleId(), context);
            if (ruleGroupInfo != null) { // 尝试缓存记录
                tryCache(ruleCacheService, ruleGroupInfo);
            }
        } else {
            ruleGroupInfo = this.getRelevantRuleMgmtService().getByRuleGroupId(ruleExtParam.getRuleId());
        }
        return ruleGroupInfo;
    }

    private void tryCache(RuleCacheService ruleCacheService, RelevantRuleGroupInfoDto ruleGroupInfo) {
        if(ruleCacheService == null || ruleGroupInfo == null) {
            return;
        }
        try {
            // 60分钟的有效期，页面加载到操作结束，足够了
            ruleCacheService.set(SdkRedisCacheUtil.getIdKey4Relevant(ruleGroupInfo.getId()), JSON.toJSONString(ruleGroupInfo));
            // MDD侧（孙敬宇）使用AppContext.cache()，只支持按秒
            ruleCacheService.expire(SdkRedisCacheUtil.getIdKey4Relevant(ruleGroupInfo.getId()), 3600);
        } catch (Exception e) {}
    }

    @NotNull
    private RuleExecuteCarrier getRuleExecuteCarrier(
            RuleExtParamDto ruleExtParamDto, Map<String, Object> condition, RelevantRuleGroupInfoDto ruleGroupInfo) {
        RelevantDataTypeEnum relevantDataType = RelevantDataTypeEnum.getByCode(ruleGroupInfo.getDataType());
        Map<String, Object> formulaData = RuleExtFormulaDataHelper.getForRelevantRule(ruleExtParamDto.getSources(), relevantDataType);
        RuleExecuteContext ctx = new RuleExecuteContext(ruleExtParamDto.getContext(), condition, null, formulaData);
        RuleExecuteFieldInfo fi = RuleExtCarrierHelper.getRelevantRuleFI(ruleGroupInfo);
        RuleExecuteCarrier carrier = new RuleExecuteCarrier(RuleSceneEnum.RELEVANT_RULE, null, ctx, fi);
        return carrier;
    }

    @NotNull
    private HashMap<String, RuleItemDataType> getFieldDataTypes(RelevantRuleGroupInfoDto ruleGroupInfo) {
        HashMap<String, com.yonyou.iuap.ruleengine.enums.RuleItemDataType> fieldDataTypes = new HashMap<>();
        for (RuleGroupItemDto ruleItem : ruleGroupInfo.getRuleItems()) {
            /**
             * 记录：If ruleGroupInfo.getDataType() == RelevantDataTypeEnum.CHARACTER_GROUP.getCode() then key = cgId.code
             * 记录：开始做特征组时沟通，MDD侧（严明、孙敬宇）反馈特征组规则需要以cgId.code标识规则项，后来又说可以统一按[type.code]_修改2
             */
            String ruleItemField = ruleItem.getType()+"."+ruleItem.getCode();
            fieldDataTypes.put(ruleItemField, com.yonyou.iuap.ruleengine.enums.RuleItemDataType.getByCode(ruleItem.getDataType()));
        }
        return fieldDataTypes;
    }

    @NotNull
    private Map<String, Object> getSourceMap(RuleExtParamDto ruleExtParamDto, int dataType) {
        Map<String, Object> sourceMap = new HashMap<>();
        for (RuleItemDto source : ruleExtParamDto.getSources()) {
            /**
             * 记录：If ruleGroupInfo.getDataType() == RelevantDataTypeEnum.CHARACTER_GROUP.getCode() then key = cgId.code
             * 记录：开始做特征组时沟通，MDD侧（严明、孙敬宇）反馈特征组规则需要以cgId.code标识规则项，后来又说可以统一按[type.code]_修改1
             */
            sourceMap.put(source.getType()+"." + source.getCode(), source.getValue());
        }
        return sourceMap;
    }
    private RelevantRuleMgmtService getRelevantRuleMgmtService() {
        if (this.ruleMgmtService == null) {
            this.ruleMgmtService = SpringContextHolder.getBean(RelevantRuleMgmtService.class);
        }

        return this.ruleMgmtService;
    }
}
